package org.jugile.jsontmpl.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jugile.jsontmpl.common.*;
import org.jugile.jsontmpl.domain.FldBase;
import org.jugile.util.Node;
import org.jugile.util.Proxy;
import org.jugile.util.Time;
import org.jugile.util.Vo;

public class Fld extends FldBase implements org.jugile.util.IDO {

	public Value createValue() {
		return d().createValue().setFld(this);
	}
	
	public List<Value> getValuesSorted() {
		return getValues().sort("id");
	}
	public Value getFirstValue() {
		return getValues().first("id");
	}
	
	
	public Object toVo(int idx) {
		Value val = getValuesSorted().get(idx);
		if (val == null) return null;
		return val.toVo(idx);
	}
	
	protected String[] params() {
		List<String> res = new ArrayList<String>();
		for (Param p : getParams().sort("num")) {
			res.add(p.getValue());
		}
		//if (!empty(getP1())) res.add(getP1());
		//if (!empty(getP2())) res.add(getP2());
		if (res.size() == 0) return null;
		String arr[] = new String[res.size()];
		for (int i = 0; i < res.size(); i++) arr[i] = res.get(i);
		return arr;
	}

	
	public Object toVo(EvalCtx ctx) {
		Value val = getFirstValue();
		ctx.setLinenum(getLinenum());
		if (empty(getCall())) {
			// return first example value
			if (val == null) return null;
			return val.toVo(0);
		}
		if (val == null) return ctx.call(getCall(),params());
		return val.toVo(ctx);
	}
	
	
	public boolean isVisible(EvalCtx ctx) {
		if (empty(getIfCall())) return true;
		if ("true".equals(getIfCall())) return true;
		if ("false".equals(getIfCall())) return false;

		String call = getIfCall();
		boolean negate = false;
		if (call.startsWith("!")) {
			call = call.substring(1);
			negate = true;
		}
		boolean res = isVisibleTest(ctx,call);
		if (negate) return !res;
		return res;
	}
	
	public boolean isVisibleTest(EvalCtx ctx, String call) {
		Object res = ctx.call(call,null);
		if (res == null) return false;
		if (res instanceof Boolean) return ((Boolean)res).booleanValue();
		return true;		
	}
	
	public boolean isMatchFilter(String filterStr) {
		if (empty(filterStr)) return true;
		if (filterStr.startsWith("!")) {
			filterStr = filterStr.substring(1);
			for (String name : filterStr.split(" ")) {
				if (name.equals(getName())) return false;
			}
			return true; // this name not found in list
		}
		for (String name : filterStr.split(" ")) {
			if (name.equals(getName())) return true;
		}
		return false; // this name not found in list
	}
	
	private int linenum = 0; // source file num
	public int getLinenum() { return linenum; }
	
	public void parse(Ctx ctx, Node n) {
		n.validateAttributes("name c if include p0 p1 p2 p3 p4");
		n.validateElements("desc obj list str bool num ts");
		linenum = n.getLinenum();
		
		setDesc(n.str("desc"));
		setCall(n.attr("c"));
		Map<String,String> attrs = n.attrs();
		Pattern rexp = Pattern.compile("p([0-9]+)");
		for (String pname : attrs.keySet()) {
			Matcher m = rexp.matcher(pname);
			if (m.matches()) {
				String num = m.group(1);
				Param p = getOrCreateParam(parseIntSafe(num));
				p.setValue(n.attr(pname));
			} 
		}
		//setP1(n.attr("p1"));
		//setP2(n.attr("p2"));
		if (!empty(n.attr("if"))) setIfCall(n.attr("if"));
		String include = n.attr("include");
		if (!empty(include)) {
			// TODO: relative include path 
			Obj o = d().parseXmlFile(ctx, include);
			createValue().setO(o);
		} else {
			// TODO: type
			for (Node v : n.children()) {
				if (d().getFldType(v.name()) != null) 
					createValue().parse(ctx,v);
			}
		}
	}
	
	public Param getOrCreateParam(int num) {
		Param p = getParams().byNum(num).unique();
		if (p == null) {
			p = d().createParam().setNum(num).setFld(this);
		}
		return p;
	}
	
	public Node toXml() {
		Node n = new Node("fld");
		n.attr("name", getName());
		if (!empty(getCall())) n.attr("c", getCall());
		for (Param p : getParams().sort("num")) {
			n.attr("p"+p.getNum(),p.getValue());
		}
		//if (!empty(getP1())) n.attr("p1", getP1());
		//if (!empty(getP2())) n.attr("p2", getP2());		
		if (getType() != null) n.attr("type", getType());
		if (getIfCall() != null) n.attr("if", getIfCall());
		if (!empty(getDesc())) n.set("desc", getDesc());
		for (Value v : getValuesSorted()) n.add(v.toXml());
		return n;
	}
	
	public void onDelete() {
		for (Value v : getValues()) v.delete();
	}
	
	public Obj getRootObj() {
		//return getObj().getRootObj();
		return null; // TODO: implement easy way to get real root object
	}
	
}